/**
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package us.levk.remote.drmaa.server.servlet;

import java.io.IOException;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.servlet.ServletConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;
import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.XmlRpcRequest;
import org.apache.xmlrpc.common.XmlRpcHttpRequestConfigImpl;
import org.apache.xmlrpc.server.AbstractReflectiveHandlerMapping;
import org.apache.xmlrpc.server.PropertyHandlerMapping;
import org.apache.xmlrpc.server.XmlRpcHandlerMapping;
import org.apache.xmlrpc.server.XmlRpcServerConfigImpl;
import org.apache.xmlrpc.webserver.XmlRpcServlet;
import org.apache.xmlrpc.webserver.XmlRpcServletServer;

import us.levk.remote.drmaa.common.communication.ExtendedTypeConverterFactory;
import us.levk.remote.drmaa.common.communication.ExtendedTypeFactory;
import us.levk.remote.drmaa.server.Configuration;

/**
 * @author levk
 *
 */
@WebServlet (name="RemoteDrmaaXmlRpcServlet", urlPatterns="/xmlrpc", asyncSupported=true)
public class RemoteDrmaaXmlRpcServlet extends XmlRpcServlet {
  private static final long serialVersionUID = 1L;

  private static final transient Logger LOG = Logger.getLogger (RemoteDrmaaXmlRpcServlet.class);

  private static final transient String LOGIN_CONFIGURATION_NAME = "RemoteDrmaaXmlRpcServlet";
  private static final transient String SESSION_USER_ATTRIBUTE = "us.levk.remote.drmaa.server.servlet.RemoteDrmaaXmlRpcServlet.user";

  /* (non-Javadoc)
   * @see org.apache.xmlrpc.webserver.XmlRpcServlet#newXmlRpcHandlerMapping()
   */
  @Override protected XmlRpcHandlerMapping newXmlRpcHandlerMapping () throws XmlRpcException {
    PropertyHandlerMapping mapping = (PropertyHandlerMapping) super.newXmlRpcHandlerMapping ();

    if (Configuration.getConfiguration ().isAuthenticationEnabled ()) {
      mapping.setAuthenticationHandler (new AbstractReflectiveHandlerMapping.AuthenticationHandler () {
  
        @Override public boolean isAuthorized (XmlRpcRequest arg0) throws XmlRpcException {
          final ExtendedXmlRpcHttpRequestConfig config = (ExtendedXmlRpcHttpRequestConfig) arg0.getConfig ();
  
          HttpSession session = config.request.getSession ();
          if (session.getAttribute (SESSION_USER_ATTRIBUTE) != null) return true;
          else try {
            LoginContext lc = new LoginContext (LOGIN_CONFIGURATION_NAME,
                                                new Subject (),
                                                new CallbackHandler () {

                                                  @Override public void handle (Callback[] callbacks) 
                                                      throws IOException, UnsupportedCallbackException {
                                                    for (Callback callback : callbacks)
                                                      if (callback instanceof NameCallback) 
                                                        ((NameCallback) callback).setName (config.getBasicUserName ());
                                                      else if (callback instanceof PasswordCallback) 
                                                        ((PasswordCallback) callback).setPassword (config.getBasicPassword ().toCharArray ());
                                                      else {
                                                        UnsupportedCallbackException e = new UnsupportedCallbackException (callback);
                                                        LOG.warn ("Unsupported callback", e);
                                                        throw e;
                                                      }
                                                  }
                                                },
                                                new javax.security.auth.login.Configuration () {
                                                  
                                                  @Override public AppConfigurationEntry[] getAppConfigurationEntry (String name) {
                                                    return Configuration.getConfiguration ().getAuthenticationConfigurationEntries ();
                                                  }
                                                });
            lc.login ();
            LOG.trace ("Successful login for user " + config.getBasicUserName ());
            session.setAttribute (SESSION_USER_ATTRIBUTE, config.getBasicUserName ());
            return true;
          } catch (LoginException e) {
            LOG.warn ("Failed on login", e);
            return false; 
          }
        }
      });
    }

    return mapping;
  }

  /**
   * @author levk
   *
   */
  private class ExtendedXmlRpcHttpRequestConfig extends XmlRpcHttpRequestConfigImpl {
    final HttpServletRequest request;

    ExtendedXmlRpcHttpRequestConfig (HttpServletRequest request) { this.request = request; }

    /* (non-Javadoc)
     * @see org.apache.xmlrpc.common.XmlRpcHttpRequestConfigImpl#isEnabledForExceptions()
     */
    @Override public boolean isEnabledForExceptions () { return true; }

    /* (non-Javadoc)
     * @see org.apache.xmlrpc.XmlRpcConfigImpl#isEnabledForExtensions()
     */
    @Override public boolean isEnabledForExtensions () { return true; }
  }

  /* (non-Javadoc)
   * @see org.apache.xmlrpc.webserver.XmlRpcServlet#newXmlRpcServer(javax.servlet.ServletConfig)
   */
  @Override protected XmlRpcServletServer newXmlRpcServer (ServletConfig pConfig) throws XmlRpcException { 
    return new XmlRpcServletServer () {

      { 
        setTypeFactory (new ExtendedTypeFactory (this));
        setTypeConverterFactory (new ExtendedTypeConverterFactory ());
        setConfig (new XmlRpcServerConfigImpl () { // This is annoying, but there's something wrong with the order things are loaded

          /* (non-Javadoc)
           * @see org.apache.xmlrpc.XmlRpcConfigImpl#isEnabledForExtensions()
           */
          @Override public boolean isEnabledForExtensions () { return true; }

          /* (non-Javadoc)
           * @see org.apache.xmlrpc.server.XmlRpcServerConfigImpl#isEnabledForExceptions()
           */
          @Override public boolean isEnabledForExceptions () { return true; }
        });
      }

      /* (non-Javadoc)
       * @see org.apache.xmlrpc.webserver.XmlRpcServletServer#newConfig(javax.servlet.http.HttpServletRequest)
       */
      @Override protected XmlRpcHttpRequestConfigImpl newConfig (HttpServletRequest pRequest) {
        return new ExtendedXmlRpcHttpRequestConfig (pRequest);
      }
    };
  }
}
